<?php

/**
 * This class has been deprecated, please use Fete_Db_Table, Fete_Model_DataSet
 * and Fete_Model_DataRow instead.
 * 
 * @author Nguyen Duc Thuan <me@ndthuan.com>
 * @copyright Nguyen Duc Thuan <me@ndthuan.com>
 * @deprecated
 */
abstract class Fete_Model
{
    /**
     * @var Fete_Db
     */
    static protected $_defaultDb = null;

    /**
     *
     * @var Fete_Application
     */
    static protected $_app = null;

    /**
     *
     * @var array
     */
    static protected $_loadedModels = array();

    /**
     * @var array
     */
    protected $_validationRules = array();

    /**
     *
     * @var array
     */
    protected $_validationStates = array();

    /**
     *
     * @var array
     */
    protected $_filters = array();

    /**
     *
     * @var boolean
     */
    protected $_hasErrors = false;

    /**
     *
     * @var Fete_Db
     */
    protected $_db = null;

    /**
     *
     * @var string
     */
    protected $_name = null;

    /**
     *
     * @var string
     */
    protected $_primary = null;

    /**
     * array($table => array($field, $refTablePrimary))
     * @var array
     */
    protected $_references = array();

    /**
     * array($table => $foreignTableField)
     * @var array
     */
    protected $_foreigns = array();

    /**
     * Number of affected rows after performing DELETE/UPDATE SQL statements
     * @var @integer
     */
    protected $_affectedRows = 0;

    /**
     * ID of inserted record
     * @var integer
     */
    protected $_insertId = 0;

    /**
     * @param $data
     * @return Fete_Model
     */
    public function &insert($data)
    {
        $data = $this->filter($data, $this->_filters);
        if ($this->validate($data, $this->_validationRules)->isValid()) {
            $this->_insertId = $this->getDb()->insert($this->_name, $data, false)->getInsertId();

            foreach ($this->_foreigns as $table => $field)
            {
                self::getModelInstance($table)->onReferenceInserted($this->_name, $data);
            }

            foreach ($this->_references as $table => $fields)
            {
                self::getModelInstance($table)->onForeignInserted($this->_name, $data);
            }
        }

        return $this;
    }

    /**
     * @param $data
     * @return Fete_Model
     */
    public function &replace($data)
    {
        $data = $this->filter($data, $this->_filters);
        if ($this->validate($data, $this->_validationRules)->isValid()) {
            $this->_insertId = $this->getDb()->insert($this->_name, $data, true)->getInsertId();

            foreach ($this->_foreigns as $table => $field)
            {
                self::getModelInstance($table)->onReferenceInserted($this->_name, $data);
            }

            foreach ($this->_references as $table => $fields)
            {
                self::getModelInstance($table)->onForeignInserted($this->_name, $data);
            }
        }

        return $this;
    }

    /**
     * @param $data
     * @param $conditions
     * @return Fete_Model
     */
    public function &update($data, $conditions = array())
    {
        $data = $this->filter($data, $this->_filters);
        if ($this->validate($data, $this->_validationRules)->isValid()) {
            $this->_affectedRows = $this->getDb()->update($this->_name, $data, $conditions)->getAffectedRows();

            $rows = $this->getList($conditions);

            foreach ($this->_foreigns as $table => $field)
            {
                self::getModelInstance($table)->onReferenceUpdated($this->_name, $rows);
            }

            foreach ($this->_references as $table => $fields)
            {
                self::getModelInstance($table)->onForeignUpdated($this->_name, $rows);
            }
        }

        return $this;
    }

    /**
     *
     * Delete records
     * @param $conditions
     * @return Fete_Model
     */
    public function &delete($conditions = array())
    {
        $rows = $this->getList($conditions);

        $this->_affectedRows = $this->getDb()->delete($this->_name, $conditions)->getAffectedRows();

        foreach ($this->_foreigns as $table => $field)
        {
            self::getModelInstance($table)->onReferenceDeleted($this->_name, $rows);
        }

        foreach ($this->_references as $table => $fields)
        {
            self::getModelInstance($table)->onForeignDeleted($this->_name, $rows);
        }

        return $this;
    }

    public function onForeignInserted($foreignTableName, $row)
    {}

    public function onReferenceInserted($referenceTableName, $insertedId = 0)
    {}

    public function onForeignUpdated($foreignTableName, $updatedRows = array())
    {}

    public function onReferenceUpdated($referenceTableName, $updatedRows = array())
    {}

    public function onForeignDeleted($foreignTableName, $deletedRows = array())
    {}

    public function &onReferenceDeleted($referenceTableName, $deletedRows = array())
    {
        foreach ($deletedRows as $row)
        {
            $this->delete($this->_references[$referenceTableName][0] . ' = \'' . $row[$this->_references[$referenceTableName][1]] . '\'');
        }

        return $this;
    }

    public function getList($conditions = array(), $sortColumns = array(), $pageNum = 0, $perPage = 100, $fields = '*')
    {
        $conditions = $this->getDb()->buildWhere($conditions);
        $sortColumns = $this->getDb()->buildSort($sortColumns);

        if (0 === $pageNum) {
            return $this->getDb()->getAll("SELECT $fields FROM $this->_name $conditions $sortColumns", MYSQL_ASSOC);
        }

        $totalRows = $this->getDb()->getValue("SELECT COUNT($this->_primary) FROM $this->_name $conditions");

        if (0 == $totalRows) {
            $totalPages = 1;
        } else {
            $totalPages = ceil($totalRows/$perPage);
        }

        if ($pageNum < 1) {
            $pageNum = 1;
        } elseif ($pageNum > $totalPages) {
            $pageNum = $totalPages;
        }

        return array(
            'pageNum'       => $pageNum,
            'totalPages'    => $totalPages,
            'totalRows'     => $totalRows,
            'rows'          => $this->getDb()->getAll("SELECT $fields FROM $this->_name $conditions $sortColumns LIMIT " . ($pageNum - 1) * $perPage . ", $perPage", MYSQL_ASSOC)
        );
    }

    public function get($conditions = array(), $sortColumns = array(), $fields = '*')
    {
        $conditions = $this->getDb()->buildWhere($conditions);
        $sortColumns = $this->getDb()->buildSort($sortColumns);
        return $this->getDb()->getFirst("SELECT $fields FROM $this->_name $conditions $sortColumns LIMIT 1", MYSQL_ASSOC);
    }

    public function init()
    {
    }

    /**
     *
     * Set foreign key of this table
     * @param $childTable
     * @param $referenceField
     * @return Fete_Model
     */
    public function &setForeign($childTable, $referenceField)
    {
        $this->_foreigns[$childTable] = $referenceField;

        return $this;
    }

    /**
     * @return Fete_Db
     */
    public function &getDb()
    {
        if (null === $this->_db) {
            if (null === self::$_defaultDb) {
                throw new Fete_Model_Exception('There is no db adapter');
                return false;
            } else {
                $this->_db = self::$_defaultDb;
            }
        }

        return $this->_db;
    }

    /**
     *
     * @param Fete_Db $db
     * @return Fete_Model
     */
    public function &setDbHandler(Fete_Db $db)
    {
        $this->_db = $db;
        return $this;
    }

    /**
     *
     * Check the data if they are valid
     *
     * @param $data
     * @return Fete_Model
     */
    public function &validate($data, $rules)
    {
        foreach ($rules as $field => $ruleset)
        {
            $validationResult = array();
            if (!isset($data[$field])) {
                if ($ruleset instanceof Fete_Validator) {
                    $validationResult[$ruleset->getName()] = false;
                } elseif (is_array($ruleset)) {
                    foreach ($ruleset as $rule)
                    {
                        if ($rule instanceof Fete_Validator) {
                            $validationResult[$rule->getName()] = false;
                        } else {
                            $colonPos = strpos($rule, ':');
                            if (false !== $colonPos) {
                                $rule = substr($rule, 0, $colonPos);
                            }
                            $validationResult[$rule] = false;
                        }
                    }
                } else {
                    $colonPos = strpos($ruleset, ':');
                    if (false !== $colonPos) {
                        $ruleset = substr($ruleset, 0, $colonPos);
                    }
                    $validationResult[$ruleset] = false;
                }
                $this->_hasErrors = true;
            } else {
                if($ruleset instanceof Fete_Validator) {
                    $check = $ruleset->isValid($data[$field]);
                    $validationResult[$ruleset->getName()] = $check;

                    if (false === $check) {
                        $this->_hasErrors = true;
                    }
                } elseif (is_array($ruleset)) {
                    foreach ($ruleset as $rule)
                    {
                        if ($rule instanceof Fete_Validator) {
                            $check = $rule->isValid($data[$field]);
                            $validationResult[$rule->getName()] = $check;
                        } else {
                            $check = call_user_func(array(Fete_Validator::getValidator($rule), 'isValid'), $data[$field]);
                            $colonPos = strpos($rule, ':');
                            if (false !== $colonPos) {
                                $rule = substr($rule, 0, $colonPos);
                            }
                            $validationResult[$rule] = $check;
                        }

                        if (false === $check) {
                            $this->_hasErrors = true;
                        }
                    }
                } else {
                    $check = call_user_func(array(Fete_Validator::getValidator($ruleset), 'isValid'), $data[$field]);
                    $colonPos = strpos($ruleset, ':');
                    if (false !== $colonPos) {
                        $ruleset = substr($ruleset, 0, $colonPos);
                    }
                    $validationResult[$ruleset] = $check;

                    if (false === $check) {
                        $this->_hasErrors = true;
                    }
                }
            }
            $this->_validationStates[$field] = $validationResult;
        }

        return $this;
    }

    public function filter($data, $filters)
    {
        foreach ($filters as $field => $filterset)
        {
            if (isset($data[$field])) {
                if (is_array($filterset)) {
                    foreach ($filterset as $filter)
                    {
                        if ($filter instanceof Fete_Filter) {
                            $data[$field] = $filter->filter($data[$field]);
                        } else {
                            $data[$field] = Fete_Filter::getFilter($filter)->filter($data[$field]);
                        }
                    }
                } elseif ($filterset instanceof Fete_Filter) {
                    $data[$field] = $filterset->filter($data[$field]);
                } else {
                    $data[$field] = Fete_Filter::getFilter($filterset)->filter($data[$field]);
                }
            }
        }

        return $data;
    }

    /**
     *
     * @param string $field
     * @param string $validator
     * @param bool $isValid
     * @return Fete_Model
     */
    public function &setValidationState($field, $validator, $isValid)
    {
        if ($validator instanceof Fete_Validator) {
            $validator = $validator->getName();
        } elseif (is_string($validator) && false !== strpos($validator, ':')) {
            $validator = substr($validator, 0, strpos($validator, ':'));
        }
        if (!isset($this->_validationStates[$field])) {
            $this->_validationStates[$field] = array();
        }
        $this->_validationStates[$field][$validator] = $isValid;
        if (!$isValid) {
            $this->_hasErrors = true;
        }
        return $this;
    }

    public function getValidationState($field)
    {
        if (!isset($this->_validationStates[$field])) {
            $this->_validationStates[$field] = array();
        }
        return $this->_validationStates[$field];
    }

    public function isValid()
    {
        return !$this->_hasErrors;
    }

    public function getAffectedRows()
    {
        return $this->_affectedRows;
    }

    public function getInsertId()
    {
        return $this->_insertId;
    }

    /**
     *
     * Get instance of a model
     * @param $name
     * @return Fete_Model
     */
    static public function &getModelInstance($name)
    {
        $className = ucwords(str_replace('_', ' ', $name));
        $className = str_replace(' ', '', $className);
        $className .= 'Model';

        if (isset(self::$_loadedModels[$className])) {
            return self::$_loadedModels[$className];
        }

        Fete_Loader::loadClass($className);

        if (class_exists($className, false)) {
            $instance = new $className();
            $instance->init();

            self::$_loadedModels[$className] = &$instance;
            return $instance;
        }

        throw new Fete_Model_Exception('Model ' . $className . ' does not exist');
        return false;
    }

    /**
     *
     * @param string $field
     * @param Fete_Validator|string $validator
     * @return Fete_Model
     */
    public function &addValidator($field, $validator)
    {
        if (isset($this->_validationRules[$field])) {
            if (is_array($this->_validationRules[$field])) {
                $this->_validationRules[$field][] = $validator;
            } else {
                $this->_validationRules[$field] = array($this->_validationRules[$field], $validator);
            }
        } else {
            $this->_validationRules[$field] = array($validator);
        }

        return $this;
    }

    /**
     *
     * @param string $field
     * @param Fete_Filter|string $filter
     * @return Fete_Model
     */
    public function &addFilter($field, $filter)
    {
        if (isset($this->_filters[$field])) {
            if (is_array($this->_filters[$field])) {
                $this->_filters[$field][] = $filter;
            } else {
                $this->_filters[$field] = array($this->_filters[$field], $filter);
            }
        } else {
            $this->_filters[$field] = array($filter);
        }

        return $this;
    }

    public function getValidators()
    {
        return $this->_validationRules;
    }

    public function getFilters()
    {
        return $this->_filters;
    }

    /**
     *
     * Set default db adapter for model classes
     * @param Fete_Db $db
     * @return void
     */
    static public function setDefaultDb(Fete_Db &$db)
    {
        self::$_defaultDb = $db;
    }

    /**
     *
     * @return Fete_Db
     */
    static public function getDefaultDb()
    {
        return self::$_defaultDb;
    }

    static public function setApp(Fete_Application $app)
    {
        self::$_app = $app;
    }
}
